// Copyright (c) 2017 Presto Labs Pte. Ltd.
// Author: jhkim

#pragma once

#include <vector>
#include <utility>
#include <stdint.h>
#include <algorithm>
#include "fastfeature/feature_feed.pb.h"
#include "presto/quant/symbology/symbol.h"
#include "coin2/exchange/sim/order_v1/light_book_util.h"

namespace coin2::exchange::sim::order_v1 {

template <int kMaxLevel, typename PriceType = double>
struct BookContainer {
  BookContainer() {
    ask_price_.fill({});
    bid_price_.fill({});
    ask_qty_.fill(0);
    bid_qty_.fill(0);
  }

  // slow. do not use in prod
  void PrintDebugString() const {
    for (int i = 0; i < num_ask_levels_; i++) {
      LOG(INFO) << "- " << ask_price_[i] << " Q " << ask_qty_[i];
    }
    for (int i = 0; i < num_bid_levels_; i++) {
      LOG(INFO) << "+ " << bid_price_[i] << " Q " << bid_qty_[i];
    }
  }

  void UpdateBook(const ::fastfeature::FeedMessage& feed) {
    const ::fastfeature::BookMessage& book = feed.book();

    num_ask_levels_ = std::min(book.ask_size(), kMaxLevel);
    for (int i = 0; i < num_ask_levels_; i++) {
      const ::fastfeature::BookEntry& level = book.ask(i);
      ask_price_[i] = level.price_float();
      ask_qty_[i] = level.qty_float();
    }

    num_bid_levels_ = std::min(book.bid_size(), kMaxLevel);
    for (int i = 0; i < num_bid_levels_; i++) {
      const ::fastfeature::BookEntry& level = book.bid(i);
      bid_price_[i] = level.price_float();
      bid_qty_[i] = level.qty_float();
    }
    // PrintDebugString();
  }

  void UpdateFromTrade(
     const ::fastfeature::FeedMessage& feed,
     bool update_liquidity_harsh) {
    const ::fastfeature::TradeMessage& trade = feed.trade();
    if (trade.side() == ::fastfeature::TRADE_BUY_SIDE) {
      for (int i = 0; i < num_ask_levels_; ++i) {
        if (update_liquidity_harsh && ask_price_[i] < trade.price_float()) {
          ask_qty_[i] = 0;
        } else if (ask_price_[i] == trade.price_float()) {
          ask_qty_[i] -= trade.qty_float();
        } else {
          break;
        }
        if (ask_qty_[i] <= 0) ask_qty_[i] = 0;
        // let's keep at least 1 level of sided book
        if (ask_qty_[i] <= 0 && num_ask_levels_ > 1) {
          --num_ask_levels_;
          for (int j = i; j < num_ask_levels_; ++j) {
            ask_price_[j] = ask_price_[j + 1];
            ask_qty_[j] = ask_qty_[j + 1];
          }
        }
      }
    } else if (trade.side() == ::fastfeature::TRADE_SELL_SIDE) {
      for (int i = 0; i < num_bid_levels_; ++i) {
        if (update_liquidity_harsh && bid_price_[i] > trade.price_float()) {
          bid_qty_[i] = 0;
        } else if (bid_price_[i] == trade.price_float()) {
          bid_qty_[i] -= trade.qty_float();
        } else {
          break;
        }
        // let's keep at least 1 level of sided book
        if (bid_qty_[i] <= 0) bid_qty_[i] = 0;
        if (bid_qty_[i] <= 0 && num_bid_levels_ > 1) {
          --num_bid_levels_;
          for (int j = i; j < num_bid_levels_; ++j) {
            bid_price_[j] = bid_price_[j + 1];
            bid_qty_[j] = bid_qty_[j + 1];
          }
        }
      }
    }
    // PrintDebugString();
  }

  void CopyFrom(const BookContainer<kMaxLevel, PriceType>& other) {
    num_ask_levels_ = other.num_ask_levels_;
    num_bid_levels_ = other.num_bid_levels_;
    ask_price_ = other.ask_price_;
    ask_qty_ = other.ask_qty_;
    bid_price_ = other.bid_price_;
    bid_qty_ = other.bid_qty_;
  }

  void CopyFromVector(
      const std::vector<std::pair<PriceType, double>>& askpqs,
      const std::vector<std::pair<PriceType, double>>& bidpqs) {
    num_ask_levels_ = askpqs.size();
    num_bid_levels_ = bidpqs.size();
    for (int i = 0; i < num_ask_levels_; ++i) {
      ask_price_[i] = askpqs[i].first;
      ask_qty_[i] = askpqs[i].second;
    }
    for (int i = 0; i < num_bid_levels_; ++i) {
      bid_price_[i] = bidpqs[i].first;
      bid_qty_[i] = bidpqs[i].second;
    }
  }

  int num_ask_levels_ = 0;
  int num_bid_levels_ = 0;
  std::array<PriceType, kMaxLevel> ask_price_;
  std::array<double, kMaxLevel> ask_qty_;
  std::array<PriceType, kMaxLevel> bid_price_;
  std::array<double, kMaxLevel> bid_qty_;
};

template <int kMaxLevel = 10, typename PriceType = double>
class LightBook {
 public:
  typedef BookContainer<kMaxLevel, PriceType> BcType;
  typedef LightBook<kMaxLevel, PriceType> LbType;

  LightBook() : ready_(false), book_() { }
  explicit LightBook(bool use_exchange_timestamp) :
    ready_(false),
    book_(),
    use_exchange_timestamp_(use_exchange_timestamp) { }

  void CopyFrom(const LightBook& other) {
    ready_ = other.ready_;
    fetched_time = other.fetched_time;
    book_.CopyFrom(other.book_);
  }

  void InterpolateFrom(
      int64_t order_arrival_time,
      const LbType& prebook,
      const LbType& nextbook) {
    int64_t timediff_pre = order_arrival_time - prebook.fetched_time;
    if (timediff_pre < 0) {
      timediff_pre = 0;
    }
    int64_t timediff_next = nextbook.fetched_time - order_arrival_time;
    if (timediff_next < 0) {
      timediff_next = 0;
    }


    std::vector<std::pair<PriceType, double>> ask_pqs;
    std::vector<std::pair<PriceType, double>> bid_pqs;
    for (int sign : {-1, 1}) {
      double pre_price = (sign == 1) ? prebook.bid_price(0) : prebook.ask_price(0);
      double next_price = (sign == 1) ? nextbook.bid_price(0) : nextbook.ask_price(0);

      double interpol_price = next_price;

      if (timediff_pre + timediff_next > 0) {
        interpol_price = (
            pre_price * timediff_next + next_price * timediff_pre) / (
                timediff_next + timediff_pre);
      }

      if (sign == 1) {
        const BcType* bidbook = nullptr;
        // next book has more liquidity inside
        if (prebook.bid_price(0) <= nextbook.bid_price(0)) {
          bidbook = &nextbook.book_;
        } else {
          bidbook = &prebook.book_;
        }
        for (int i = 0; i < bidbook->num_bid_levels_; ++i) {
          if (bidbook->bid_price_[i] <= interpol_price || i == bidbook->num_bid_levels_ - 1) {
            bid_pqs.push_back(std::make_pair(
                bidbook->bid_price_[i],
                bidbook->bid_qty_[i]));
          }
        }
      } else {
        const BcType* askbook = nullptr;
        // next book has more liquidity inside
        if (prebook.ask_price(0) >= nextbook.ask_price(0)) {
          askbook = &nextbook.book_;
        } else {
          askbook = &prebook.book_;
        }
        for (int i = 0; i < askbook->num_ask_levels_; ++i) {
          if (askbook->ask_price_[i] >= interpol_price || i == askbook->num_ask_levels_ - 1) {
            ask_pqs.push_back(std::make_pair(
                askbook->ask_price_[i],
                askbook->ask_qty_[i]));
          }
        }
      }
    }

    fetched_time = order_arrival_time;
    ready_ = true;
    book_.CopyFromVector(ask_pqs, bid_pqs);
  }

  static void Assign(LightBook&& lhs, const LightBook& other) {
    lhs.CopyFrom(other);
  }

  bool Ready() const {
    return ready_;
  }

  void UpdateBook(const ::fastfeature::FeedMessage& feed) {
    CHECK_EQ(feed.feed_type(), ::fastfeature::BOOK_FEED);
    ready_ = true;
    if (use_exchange_timestamp_ && feed.has_exchange_time()){
      fetched_time = feed.exchange_time();
    } else {
      fetched_time = feed.fetched_time();
    }
    book_.UpdateBook(feed);
  }

  void UpdateFromTrade(const ::fastfeature::FeedMessage& feed, bool use_harsh_liquidity) {
    CHECK_EQ(feed.feed_type(), ::fastfeature::TRADE_FEED);
    if (use_exchange_timestamp_ && feed.has_exchange_time()){
      fetched_time = feed.exchange_time();
    } else {
      fetched_time = feed.fetched_time();
    }
    book_.UpdateFromTrade(feed, use_harsh_liquidity);
  }

  bool IsAskBookAvail() const {
    return book_.num_ask_levels_ > 0;
  }

  bool IsBidBookAvail() const {
    return book_.num_bid_levels_ > 0;
  }

  int num_ask_levels() const {
    return book_.num_ask_levels_;
  }

  int num_bid_levels() const {
    return book_.num_bid_levels_;
  }

  double ask_qty(int index) const {
    DCHECK_LT(index, book_.num_ask_levels_);
    return book_.ask_qty_[index];
  }

  PriceType ask_price(int index) const {
    DCHECK_LT(index, book_.num_ask_levels_);
    return book_.ask_price_[index];
  }

  double bid_qty(int index) const {
    DCHECK_LT(index, book_.num_bid_levels_);
    return book_.bid_qty_[index];
  }

  PriceType bid_price(int index) const {
    DCHECK_LT(index, book_.num_bid_levels_);
    return book_.bid_price_[index];
  }

  int64_t fetched_time = 0L;

 private:
  bool ready_;
  BcType book_;
  bool use_exchange_timestamp_{false};
  DISALLOW_COPY_AND_ASSIGN(LightBook);
};

}  // namespace coin2::exchange::sim::order_v1
